Springboot自动加载的原理

您所在的位置:网站首页 springboot 自动装载 Springboot自动加载的原理

Springboot自动加载的原理

2023-08-21 02:04| 来源: 网络整理| 查看: 265

一.概括

我们用Springboot很方便的能将一个框架给搭建起来,是因为它将以前我们需要手动配置的地方都利用自动配置来代替,利用约定大于配置的思想简化了我们开发工作量。例如:在没有springboot之前,我们要在工程里面连接数据库的时候,我们需要在applicationContext.xml文件里面配置:

但引入springboot后不需要声明DriverManagerDataSource,只需要在applicationContext.properties里面配置连接数据库的所需要的URL,用户名和密码就可以了,然后在pom文件里面引入相应的jar包就可以了,至于为什么可以这样,下面会讲解到

spring.datasource.druid.url=jdbc:mysql://127.0.01:3306/test?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true spring.datasource.druid.username=root spring.datasource.druid.password=123456 spring.datasource.driver-class-name=com.mysql.jdbc.Driver

所以自动加载对于springboot来说很重要。 springboot启动类

import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }

@SpringBootApplication注解,@SpringBootApplication是一个复合型注解,里面包含了三个很重要的注解:@SpringBootConfiguration:说明DemoApplication这个类是一个ioc容器配置类,可以在DemoApplication类中声明一些bean,然后通过@ComponentScan加载到ioc容器中,相当于我们配置文件里面的

.......

@ComponentScan:扫描当前包下的所有类和当前包下面子包所包含的类,将带有注解的类加载到ioc容器,@ComponentScan在springboot中的作用就是将代码中带有@Controller,@Service,@Repority等这些注解的类加载到ioc容器中。相当于我们在配置文件中写的这样一段代码

@EnableAutoConfiguration:基于你配置的依赖项,也就是引入的jar包,扫描所有jar包下面的META-INF/spring.factories,spring.factories中都是这个jar的配置类,配置类里面就有我们所需要的工具类。将所有复合自动配置条件的bean定义加载到ioc容器中,记住@EnableAutoConfiguration自动加载的是一些不需要我们自己去定义但是需要用到的“工具类”,例如上面提到的DriverManagerDataSource类。 @EnableAutoConfiguration能自动加载我们项目中所需要的“工具类”是用到了SpringFactoriesLoader,SpringFactoriesLoader能将指定的配置文件META-INF/spring.factories加载配置。

@Target({ElementType.TYPE})// 注解的适用范围,其中TYPE用于描述类、接口(包括包注解类型)或enum声明 @Retention(RetentionPolicy.RUNTIME)// 注解的生命周期,保留到class文件中 @Documented// 表明这个注解应该被javadoc记录 @Inherited// 子类可以继承该注解 @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan( excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )} ) public @interface SpringBootApplication { @AliasFor( annotation = EnableAutoConfiguration.class ) Class[] exclude() default {}; @AliasFor( annotation = EnableAutoConfiguration.class ) String[] excludeName() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "basePackages" ) String[] scanBasePackages() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "basePackageClasses" ) Class[] scanBasePackageClasses() default {}; }

二.@EnableAutoConfiguration的基本原理 来看看@EnableAutoConfiguration这个注解是如何实现

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(EnableAutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; /** * Exclude specific auto-configuration classes such that they will never be applied. * @return the classes to exclude */ Class[] exclude() default {}; /** * Exclude specific auto-configuration class names such that they will never be * applied. * @return the class names to exclude * @since 1.3.0 */ String[] excludeName() default {}; }

在@EnableAutoConfiguration中有两个比较重要的注解,一个是@AutoConfigurationPackage,另外一个是@Import(EnableAutoConfigurationImportSelector.class) @AutoConfigurationPackage的作用就是将当前启动类所在的包以bean的形式注册到ioc容器中,至于在后面起什么作用,我在网上查找了半天,也没有一个说清楚的,有谁知道的,请在下面留言告诉我。 @Import(EnableAutoConfigurationImportSelector.class):借助EnableAutoConfigurationImportSelector父类AutoConfigurationImportSelector的selectImports方法来读取所有依赖的jar包下面META-INF/spring.factories文件,并且根据加载条件来加载项目所需要的类,这样就完成了springboot的自动加载。 EnableAutoConfigurationImportSelector继承了AutoConfigurationImportSelector,看看AutoConfigurationImportSelector的selectImports方法

/** * 最主要的方法 * annotationMetadata * [@org.springframework.boot.autoconfigure.SpringBootApplication * (scanBasePackageClasses=[], excludeName=[], exclude=[], scanBasePackages=[])] * @param annotationMetadata * @return */ @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } /** * */ AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); /** * 得到注解中的所有属性信息{excludeName=[], exclude=[]} */ AnnotationAttributes attributes = getAttributes(annotationMetadata); /** *加载META-INF/spring-autoconfigure-metadata.properties,获取所有支持自动配置的信息 * 获取所有支持EnableAutoConfiguration的组件信息,这部分信息配置在spring-boot-autoconfig包下的spring.factories下 * * 使用了内部工具使用SpringFactoriesLoader,查找classpath上所有jar包中的 * META-INF\spring.factories,找出其中key为 * org.springframework.boot.autoconfigure.EnableAutoConfiguration * 的属性定义的工厂类名称。 */ List configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); /** * 去除不需要的 * @EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class, RedisAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, }) */ Set exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); /** * 然后使用AutoConfigurationImportFilter进行过滤,过滤的方式基本上是判断现有系统是否引入了某个组件,(系统是否使用哪个组件是在pom定义的时候就确定了的) * ,如果有的话则进行相关配置。比如ServletWebServerFactoryAutoConfiguration * ,会在ServletRequest.class等条件存在的情况下进行配置, * 而EmbeddedTomcat会在Servlet.class, Tomcat.class存在的情况下创建TomcatServletWebServerFactory * * org.springframework.boot.autoconfigure.condition.OnClassCondition * 总而言之,此过滤器会检查候选配置类的注解@ConditionalOnClass,如果要求的类在classpath 中不存在,则这个候选配置类会被排除掉 */ configurations = filter(configurations, autoConfigurationMetadata); /** * 现在已经找到所有需要被应用的候选配置类 * 广播事件AutoConfigurationImportEvent */ fireAutoConfigurationImportEvents(configurations, exclusions); return StringUtils.toStringArray(configurations); } private void fireAutoConfigurationImportEvents(List configurations, Set exclusions) { List listeners = getAutoConfigurationImportListeners(); if (!listeners.isEmpty()) { AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions); for (AutoConfigurationImportListener listener : listeners) { invokeAwareMethods(listener); listener.onAutoConfigurationImportEvent(event); } } }

getCandidateConfigurations方法会读取到所有依赖jar包下面的META-INF/spring.factories,并将spring.factories中的配置类的全名称获取到。

protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct."); return configurations; }

重点来了,springboot之所以能拿到spring.factories就是通过SpringFactoriesLoader来读取的,SpringFactoriesLoader会将依赖包所有的spring.factories读取出来,并用一个map来封装读取出来的vaule。SpringFactoriesLoader是spring提供的一种扩张方案,其主要功能就是从指定的配置文件META-INF/spring.factories加载配置。

public static List loadFactoryNames(Class factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } /** *在springboot启动的时候会将依赖包所有的spring.factories读取出来,spring.factories *中不仅包括配置类,还有监听器,初始化器等 */ private static Map loadSpringFactories(@Nullable ClassLoader classLoader) { //第一次就加载了所有的,后面每次需要的时候都是从缓存里面取的 MultiValueMap result = cache.get(classLoader); if (result != null) { return result; } try { Enumeration urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); //将文件路径转变成Properties Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry entry : properties.entrySet()) { String factoryClassName = ((String) entry.getKey()).trim(); for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } }

拿例如引入的mybatis举例,引入baomidou的mybaitis包后,springboot启动会去读取这个jar包下面的配置文件

com.baomidou mybatis-plus ${mybatis-plus.version}

# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.baomidou.mybatisplus.spring.boot.starter.MybatisPlusAutoConfiguration

我们再看看MybatisPlusAutoConfiguration这个配置类里面的内容,可以看到MybatisPlusAutoConfiguration里面产生的bean都是以前需要我们去xml文件里面配置的类,当有了MybatisPlusAutoConfiguration后,我们不需要去xml文件利声明这些bean,springboot在启动的时候就将这些ban加载到了容器里。

@Configuration @ConditionalOnClass({SqlSessionFactory.class, MybatisSqlSessionFactoryBean.class}) @ConditionalOnBean({DataSource.class}) @EnableConfigurationProperties({MybatisPlusProperties.class}) @AutoConfigureAfter({DataSourceAutoConfiguration.class}) public class MybatisPlusAutoConfiguration { private static final Log logger = LogFactory.getLog(MybatisPlusAutoConfiguration.class); private final MybatisPlusProperties properties; private final Interceptor[] interceptors; private final ResourceLoader resourceLoader; private final DatabaseIdProvider databaseIdProvider; private final List configurationCustomizers; public MybatisPlusAutoConfiguration(MybatisPlusProperties properties, ObjectProvider interceptorsProvider, ResourceLoader resourceLoader, ObjectProvider databaseIdProvider, ObjectProvider configurationCustomizersProvider) { this.properties = properties; this.interceptors = (Interceptor[])interceptorsProvider.getIfAvailable(); this.resourceLoader = resourceLoader; this.databaseIdProvider = (DatabaseIdProvider)databaseIdProvider.getIfAvailable(); this.configurationCustomizers = (List)configurationCustomizersProvider.getIfAvailable(); } @PostConstruct public void checkConfigFileExists() { if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) { Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation()); Assert.state(resource.exists(), "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)"); } } @Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean(); factory.setDataSource(dataSource); factory.setVfs(SpringBootVFS.class); if (StringUtils.hasText(this.properties.getConfigLocation())) { factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); } MybatisConfiguration configuration = this.properties.getConfiguration(); if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) { configuration = new MybatisConfiguration(); } if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) { Iterator i$ = this.configurationCustomizers.iterator(); while(i$.hasNext()) { ConfigurationCustomizer customizer = (ConfigurationCustomizer)i$.next(); customizer.customize(configuration); } } configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class); factory.setConfiguration(configuration); if (this.properties.getConfigurationProperties() != null) { factory.setConfigurationProperties(this.properties.getConfigurationProperties()); } if (!ObjectUtils.isEmpty(this.interceptors)) { factory.setPlugins(this.interceptors); } if (this.databaseIdProvider != null) { factory.setDatabaseIdProvider(this.databaseIdProvider); } if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) { factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage()); } if (StringUtils.hasLength(this.properties.getTypeEnumsPackage())) { factory.setTypeEnumsPackage(this.properties.getTypeEnumsPackage()); } if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) { factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); } if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) { factory.setMapperLocations(this.properties.resolveMapperLocations()); } if (!ObjectUtils.isEmpty(this.properties.getGlobalConfig())) { factory.setGlobalConfig(this.properties.getGlobalConfig().convertGlobalConfiguration()); } return factory.getObject(); } @Bean @ConditionalOnMissingBean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { ExecutorType executorType = this.properties.getExecutorType(); return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory); } @Configuration @Import({MybatisPlusAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class}) @ConditionalOnMissingBean({MapperFactoryBean.class}) public static class MapperScannerRegistrarNotFoundConfiguration { public MapperScannerRegistrarNotFoundConfiguration() { } @PostConstruct public void afterPropertiesSet() { MybatisPlusAutoConfiguration.logger.debug("No " + MapperFactoryBean.class.getName() + " found."); } } public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware { private BeanFactory beanFactory; private ResourceLoader resourceLoader; public AutoConfiguredMapperScannerRegistrar() { } public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { MybatisPlusAutoConfiguration.logger.debug("Searching for mappers annotated with @Mapper"); ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); try { if (this.resourceLoader != null) { scanner.setResourceLoader(this.resourceLoader); } List packages = AutoConfigurationPackages.get(this.beanFactory); if (MybatisPlusAutoConfiguration.logger.isDebugEnabled()) { Iterator i$ = packages.iterator(); while(i$.hasNext()) { String pkg = (String)i$.next(); MybatisPlusAutoConfiguration.logger.debug("Using auto-configuration base package '" + pkg + "'"); } } scanner.setAnnotationClass(Mapper.class); scanner.registerFilters(); scanner.doScan(StringUtils.toStringArray(packages)); } catch (IllegalStateException var7) { MybatisPlusAutoConfiguration.logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled." + var7); } } public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } } }

最后当springboot拿到这些配置文件里面的全性定类名的时候,会通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。 当收集到所有在spring.factories中指定的bean的类路径,在processGroupImports方法中会以处理@Import注解一样的逻辑将其导入进容器。  

public void processGroupImports() { for (DeferredImportSelectorGrouping grouping : this.groupings.values()) { // getImports即上面得到的所有类路径的封装 grouping.getImports().forEach(entry -> { ConfigurationClass configurationClass = this.configurationClasses.get( entry.getMetadata()); try { // 和处理@Import注解一样 processImports(configurationClass, asSourceClass(configurationClass), asSourceClasses(entry.getImportClassName()), false); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configurationClass.getMetadata().getClassName() + "]", ex); } }); } } private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection importCandidates, boolean checkForCircularImports) { ... // 遍历收集到的类路径 for (SourceClass candidate : importCandidates) { ... //如果candidate是ImportSelector或ImportBeanDefinitionRegistrar类型其处理逻辑会不一样,这里不关注 // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> // process it as an @Configuration class this.importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); // 当作 @Configuration 处理 processConfigurationClass(candidate.asConfigClass(configClass)); ... } ... }

springboot是如何让applicationcon.properties里面的配置生效的呢 以MybatisPlusAutoConfiguration举例,在MybatisPlusAutoConfiguration类上面有一个EnableConfigurationProperties注解,这个注解的作用就是让使用 @ConfigurationProperties 的类进行ioc注入,也就是将MybatisPlusProperties这个属性类生效,

@Configuration @ConditionalOnClass({SqlSessionFactory.class, MybatisSqlSessionFactoryBean.class}) @ConditionalOnBean({DataSource.class}) @EnableConfigurationProperties({MybatisPlusProperties.class}) @AutoConfigureAfter({DataSourceAutoConfiguration.class}) public class MybatisPlusAutoConfiguration {

我们再看看MybatisPlusProperties这类。在这里看到了注解@ConfigurationProperties,这个注解的作用就是读取配置文件里面属性,将读到的值绑定到被注解的类中的属性。

@ConfigurationProperties( prefix = "mybatis-plus" ) public class MybatisPlusProperties { public static final String MYBATIS_PLUS_PREFIX = "mybatis-plus"; private String configLocation; private String[] mapperLocations; private String typeAliasesPackage; private String typeEnumsPackage; private String typeHandlersPackage; private boolean checkConfigLocation = false; private ExecutorType executorType; private Properties configurationProperties; @NestedConfigurationProperty private GlobalConfig globalConfig; @NestedConfigurationProperty private MybatisConfiguration configuration;

再看看项目中的配置文件application.yml,发现对mybatis的配置中是能和MybatisPlusProperties这个类一一对于对应的。

mybatis-plus: # xml文件路径在package下面的 classpath:/com/yourpackage/*/mapper/*Mapper.xml # xml文件路径在resources下面 classpath:/mapper/*Mapper.xml mapper-locations: classpath:/mappers/*Mapper.xml #扫描的pojo对象所在包 type-aliases-package: com.example.demo.bean global-config: #\u9A7C\u5CF0\u4E0B\u5212\u7EBF\u8F6C\u6362 db-column-underline: true #\u903B\u8F91\u5220\u9664\u914D\u7F6E\uFF08\u4E0B\u97623\u4E2A\u914D\u7F6E\uFF09 logic-delete-value: 1 logic-not-delete-value: 0 sql-injector: com.baomidou.mybatisplus.mapper.LogicSqlInjector

通过@ConfigurationProperties注解将配置文件里面的值读到配置bean里面,然后@EnableConfigurationProperties将注解bean注入到ioc容器中,这样当项目需要配置信息的时候可以直接从容器中去取。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3